package com.gywj.race.util.oss;

import com.aliyun.oss.*;
import com.aliyun.oss.model.*;
import com.google.common.collect.Lists;
import com.gywj.race.config.properties.OssProperties;
import com.gywj.race.util.IdWorker;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * The type Oss util.
 * 阿里云 oss 操作工具类
 *
 * @author ccg
 * @version v1.0.0
 * @since 2018 -12-05
 */
@Component
public class OssUtil {

    private final Logger logger = LoggerFactory.getLogger(OssUtil.class);

    @Autowired
    private OssProperties ossProperties;

    private static List<String> getImgContentTypeList() {
        List<String> imgContentTypeList = Lists.newArrayList();
        imgContentTypeList.add("bmp");
        imgContentTypeList.add("gif");
        imgContentTypeList.add("png");
        imgContentTypeList.add("jpeg");
        imgContentTypeList.add("jpg");
        imgContentTypeList.add("svg");
        imgContentTypeList.add("psd");
        return imgContentTypeList;
    }


    /**
     * 获取阿里云OSS客户端对象
     * 通过 ClientConfiguration 可以详细配置oss 客户端
     */
    private OSSClient getOSSClient() {
        return new OSSClient(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret(), new ClientConfiguration());
    }

    /**
     * OSS单文件上传
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @return String 文件地址
     */
    public String uploadFile(MultipartFile file, String fileType) {
        //文件名，根据UUID来
        String fileName = ossProperties.getPicLocation() + UUID.randomUUID().toString().toUpperCase().replace("-", "") + "." + fileType;
        return putObject(file, fileType, fileName);
    }

    /**
     * OSS单文件上传(带文件名的上传)
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @return String 文件地址
     */
    public String uploadFile(MultipartFile file, String fileType, String fileName) {
        //自定义文件名
        return putObject(file, fileType, fileName);
    }

    /**
     * OSS单文件上传（带压缩）
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param quality  压缩质量 0.5 - 1
     * @return String 文件地址
     */
    public String uploadFile(MultipartFile file, String fileType, float quality) {
        //文件名，根据UUID来
        String fileName = ossProperties.getPicLocation() + UUID.randomUUID().toString().toUpperCase().replace("-", "") + "." + fileType;
        return putObjectWithCompress(file, fileType, fileName, quality);
    }

    /**
     * 更新文件:只更新内容，不更新文件名和文件地址。
     * (因为地址没变，可能存在浏览器原数据缓存，不能及时加载新数据，例如图片更新，请注意)
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param oldUrl   需要更新的文件地址
     * @return String string
     */
    public String updateFile(MultipartFile file, String fileType, String oldUrl) {
        String fileName = getFileName(oldUrl);
        if (fileName == null) {
            return null;
        }
        return putObject(file, fileType, fileName);
    }

    /**
     * 更新文件:只更新内容，不更新文件名和文件地址。
     * (因为地址没变，可能存在浏览器原数据缓存，不能及时加载新数据，例如图片更新，请注意)
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param oldUrl   需要更新的文件地址
     * @param quality  压缩质量 0.5 - 1
     * @return String string
     */
    public String updateFile(MultipartFile file, String fileType, String oldUrl, float quality) {
        String fileName = getFileName(oldUrl);
        if (fileName == null) {
            return null;
        }
        return putObjectWithCompress(file, fileType, fileName, quality);
    }

    /**
     * 替换文件:删除原文件并上传新文件，文件名和地址同时替换
     * 解决原数据缓存问题，只要更新了地址，就能重新加载数据)
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param oldUrl   需要删除的文件地址
     * @return String 文件地址
     */
    public String replaceFile(MultipartFile file, String fileType, String oldUrl) {
        //先删除原文件
        boolean flag = deleteFile(oldUrl);
        if (!flag) {
            //更改文件的过期时间，让他到期自动删除。  
        }
        return uploadFile(file, fileType);
    }

    /**
     * 单文件删除
     *
     * @param fileUrl 需要删除的文件url
     * @return boolean 是否删除成功
     */
    public boolean deleteFile(String fileUrl) {
        //根据url获取bucketName
        String bucketName = OssUtil.getBucketName(fileUrl);
        //根据url获取fileName
        String fileName = OssUtil.getFileName(fileUrl);
        if (bucketName == null || fileName == null) {
            return false;
        }
        OSSClient ossClient = null;
        try {
            ossClient = new OSSClient(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
            GenericRequest request = new DeleteObjectsRequest(bucketName).withKey(fileName);
            ossClient.deleteObject(request);
        } catch (Exception oe) {
            oe.printStackTrace();
            return false;
        } finally {
            ossClient.shutdown();
        }
        return true;
    }

    /**
     * 批量文件删除(较快)：适用于相同endPoint和BucketName
     *
     * @param fileUrls 需要删除的文件url集合
     * @return int 成功删除的个数
     */
    public int deleteFile(List<String> fileUrls) {
        //成功删除的个数
        int deleteCount = 0;
        //根据url获取bucketName
        String bucketName = OssUtil.getBucketName(fileUrls.get(0));
        //根据url获取fileName
        List<String> fileNames = OssUtil.getFileName(fileUrls);
        if (bucketName == null || fileNames.size() <= 0) {
            return 0;
        }
        OSSClient ossClient = null;
        try {
            ossClient = new OSSClient(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
            DeleteObjectsRequest request = new DeleteObjectsRequest(bucketName).withKeys(fileNames);
            DeleteObjectsResult result = ossClient.deleteObjects(request);
            deleteCount = result.getDeletedObjects().size();
        } catch (OSSException oe) {
            oe.printStackTrace();
            throw new RuntimeException("OSS服务异常:", oe);
        } catch (ClientException ce) {
            ce.printStackTrace();
            throw new RuntimeException("OSS客户端异常:", ce);
        } finally {
            ossClient.shutdown();
        }
        return deleteCount;

    }

    /**
     * 批量文件删除(较慢)：适用于不同endPoint和BucketName
     *
     * @param fileUrls 需要删除的文件url集合
     * @return int 成功删除的个数
     */
    public int deleteFiles(List<String> fileUrls) {
        int count = 0;
        for (String url : fileUrls) {
            if (deleteFile(url)) {
                count++;
            }
        }
        return count;
    }

    /**
     * 上传文件
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param fileName 文件名
     * @return String
     */
    private String putObject(MultipartFile file, String fileType, String fileName) {
        OSSClient ossClient = getOSSClient();
        String url;
        try {
            InputStream input = file.getInputStream();
            // 创建上传Object的Metadata
            ObjectMetadata meta = new ObjectMetadata();
            // 设置上传内容类型
            meta.setContentType(OssUtil.contentType(fileType));
            // 被下载时网页的缓存行为
            meta.setCacheControl("no-cache");
            //创建上传请求
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), fileName, input, meta);
            ossClient.putObject(request);
            //上传成功再返回的文件路径
            url = ossProperties.getEndpoint().replaceFirst("http://", "http://" + ossProperties.getBucketName() + ".") + "/" + fileName;
        } catch (OSSException | ClientException | IOException oe) {
            oe.printStackTrace();
            return null;
        } finally {
            ossClient.shutdown();
        }
        return url;
    }

    /**
     * 上传文件
     * data base64
     *
     * @return String
     */
    public String putDataBase64Jpg(String content) {
        OSSClient ossClient = getOSSClient();
        String url;
        try {
            String suffix = content.substring(11, content.indexOf(";"));
            // 使用插件传输产生的前缀
            String prefix = content.substring(0, content.indexOf(",") + 1);
            // 替换前缀为空
            content = content.replace(prefix, "");
            // content = content.substring(content.indexOf(",") + 1);

            byte[] bytes = new BASE64Decoder().decodeBuffer(content);  //将字符串转换为byte数组，这里的content是那一串base64密文 /9j/4AAQ.................，不包含（data:img/jpg;base64,）注意标点符号
            InputStream inputStream = new ByteArrayInputStream(bytes);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(inputStream.available());

            // 创建上传Object的Metadata
            ObjectMetadata meta = new ObjectMetadata();
            // 设置上传内容类型
            meta.setContentType(OssUtil.contentType("jpg"));
            // 被下载时网页的缓存行为
            meta.setCacheControl("no-cache");
            long leastSignificantBits = new IdWorker().nextId();
            //创建上传请求
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), String.valueOf(leastSignificantBits) + ".jpg", inputStream, meta);
            ossClient.putObject(request);
            //上传成功再返回的文件路径
            url = ossProperties.getEndpoint().replaceFirst("http://", "http://" + ossProperties.getBucketName() + ".") + "/" + String.valueOf(leastSignificantBits).toString() + ".jpg";
        } catch (OSSException | ClientException | IOException oe) {
            oe.printStackTrace();
            return null;
        } finally {
            ossClient.shutdown();
        }
        return url;
    }

    public String putDataByByte(byte[] bytes) {
        OSSClient ossClient = getOSSClient();
        String url;
        try {
            InputStream inputStream = new ByteArrayInputStream(bytes);
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(inputStream.available());

            // 创建上传Object的Metadata
            ObjectMetadata meta = new ObjectMetadata();
            // 设置上传内容类型
            meta.setContentType(OssUtil.contentType("jpg"));
            // 被下载时网页的缓存行为
            meta.setCacheControl("no-cache");
            long leastSignificantBits = new IdWorker().nextId();
            //创建上传请求
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), String.valueOf(leastSignificantBits) + ".jpg", inputStream, meta);
            ossClient.putObject(request);
            //上传成功再返回的文件路径
            url = ossProperties.getEndpoint().replaceFirst("http://", "http://" + ossProperties.getBucketName() + ".") + "/" + String.valueOf(leastSignificantBits).toString() + ".jpg";
        } catch (OSSException | ClientException | IOException oe) {
            oe.printStackTrace();
            return null;
        } finally {
            ossClient.shutdown();
        }
        return url;
    }

    /**
     * 上传文件带压缩
     *
     * @param file     文件
     * @param fileType 文件后缀
     * @param fileName 文件名
     * @return String
     */
    private String putObjectWithCompress(MultipartFile file, String fileType, String fileName, float quality) {
        //如果不是图片抛出异常
        if (!getImgContentTypeList().contains(fileType)) {
            throw new ServiceException("压缩文件类型操作只限图片");
        }
        OSSClient ossClient = getOSSClient();
        String url;
        try {
            InputStream input = file.getInputStream();
            // 把图片读入到内存中
            BufferedImage bufImg = ImageIO.read(input);
            // 等比，压缩，质量减少
            bufImg = Thumbnails.of(bufImg).size(bufImg.getWidth(), bufImg.getHeight()).keepAspectRatio(true).outputQuality(quality).asBufferedImage();
            // 存储图片文件byte数组
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            // 图片写入到 ImageOutputStream
            ImageIO.write(bufImg, fileType, bos);
            input = new ByteArrayInputStream(bos.toByteArray());
            // 创建上传Object的Metadata
            ObjectMetadata meta = new ObjectMetadata();
            // 设置上传内容类型
            meta.setContentType(OssUtil.contentType(fileType));
            // 被下载时网页的缓存行为
            meta.setCacheControl("no-cache");
            //创建上传请求
            PutObjectRequest request = new PutObjectRequest(ossProperties.getBucketName(), fileName, input, meta);
            ossClient.putObject(request);
            //上传成功再返回的文件路径
            url = ossProperties.getEndpoint().replaceFirst("http://", "https://" + ossProperties.getBucketName() + ".") + "/" + fileName;
        } catch (OSSException | ClientException | IOException oe) {
            oe.printStackTrace();
            return null;
        } finally {
            ossClient.shutdown();
        }
        return url;
    }

    /**
     * 获取文件类型
     *
     * @param fileType 文件后缀
     * @return String
     */
    private static String contentType(String fileType) {
        fileType = fileType.toLowerCase();
        String contentType = "";
        switch (fileType) {
            case "bmp":
                contentType = "image/bmp";
                break;
            case "gif":
                contentType = "image/gif";
                break;
            case "png":
            case "jpeg":
            case "jpg":
                contentType = "image/jpeg";
                break;
            case "html":
                contentType = "text/html";
                break;
            case "txt":
                contentType = "text/plain";
                break;
            case "vsd":
                contentType = "application/vnd.visio";
                break;
            case "ppt":
            case "pptx":
                contentType = "application/vnd.ms-powerpoint";
                break;
            case "doc":
            case "docx":
                contentType = "application/msword";
                break;
            case "xml":
                contentType = "text/xml";
                break;
            case "mp4":
                contentType = "video/mp4";
                break;
            default:
                contentType = "application/octet-stream";
                break;
        }
        return contentType;
    }


    /**
     * 根据url获取bucketName
     *
     * @param fileUrl 文件url
     * @return String bucketName
     */
    private static String getBucketName(String fileUrl) {
        String http = "http://";
        String https = "https://";
        int httpIndex = fileUrl.indexOf(http);
        int httpsIndex = fileUrl.indexOf(https);
        int startIndex = 0;
        if (httpIndex == -1) {
            if (httpsIndex == -1) {
                return null;
            } else {
                startIndex = httpsIndex + https.length();
            }
        } else {
            startIndex = httpIndex + http.length();
        }
        int endIndex = fileUrl.indexOf(".oss-");
        return fileUrl.substring(startIndex, endIndex);
    }

    /**
     * 根据url获取fileName
     *
     * @param fileUrl 文件url
     * @return String fileName
     */
    private static String getFileName(String fileUrl) {
        String str = "aliyuncs.com/";
        int beginIndex = fileUrl.indexOf(str);
        if (beginIndex == -1) {
            return null;
        }
        return fileUrl.substring(beginIndex + str.length());
    }


    /**
     * 根据url获取fileNames集合
     *
     * @param fileUrls 文件url
     * @return List<String>  fileName集合
     */
    private static List<String> getFileName(List<String> fileUrls) {
        List<String> names = new ArrayList<>();
        for (String url : fileUrls) {
            names.add(getFileName(url));
        }
        return names;
    }

    /**
     * 获取oss服务器文件上传路径前缀
     *
     * @return
     */
    public String getUploadFilePrefix() {
        return new StringBuilder()
                .append(ossProperties.getBucketName())
                .append(".")
                .append(ossProperties.getEndpoint().replace("http://", ""))
                .append("/").toString();
    }


}
